using System;
using System.Collections.Generic;
using System.Text;
using SP.Parameter;

namespace SP.HwDeferredRS
{
    public class Reactor : SP.AbstractReactor
    {
        public Reactor()
            : base()
        {
        }

        public override string Name
        {
            get { return "HwDeferredRS"; }
        }

        public override string[] CodeFiles
        {
            get { return new string[] { ".material", ".shd" }; }
        }

        public override void InitShader(Shader shader)
        {
            if (null == shader)
                return;

            shader.Fusions.Clear();

            shader.Reactor = this.Name;
            
            // vertex fusions first
            shader.Fusions.Add(new BillboardScalingFusion().SetEnabled(false));
            shader.Fusions.Add(new TangentFlippingFusion().SetEnabled(false));
            shader.Fusions.Add(new SpriteMapFusion().SetEnabled(false));
            shader.Fusions.Add(new UVScrollingFusion().SetEnabled(false));
            shader.Fusions.Add(new HardwareSkinningFusion().SetEnabled(false));
            shader.Fusions.Add(new HardwareInstancingFusion().SetEnabled(false));

            // fragment fusions then :-)
            shader.Fusions.Add(new BaseMapFusion().SetEnabled(false));
            shader.Fusions.Add(new DirtMapFusion().SetEnabled(false));
            shader.Fusions.Add(new ReflectionMapFusion().SetEnabled(false));
            shader.Fusions.Add(new PseudoSpecularFusion().SetEnabled(false));
            shader.Fusions.Add(new NormalMapFusion().SetEnabled(false));
            shader.Fusions.Add(new LightMapFusion().SetEnabled(false));
            shader.Fusions.Add(new AlphaMapFusion().SetEnabled(false));
            shader.Fusions.Add(new DeferredLightingFusion().SetEnabled(true));
            shader.Fusions.Add(new DeferredVolumeFusion().SetEnabled(false));
            shader.Fusions.Add(new VertexAlphaFusion().SetEnabled(false));
            shader.Fusions.Add(new DistanceAlphaFusion().SetEnabled(false));
            shader.Fusions.Add(new DistortionFusion().SetEnabled(false));
        }

        public override void Generate(Shader shader, string codeFile, StringBuilder b)
        {
            mShader = shader;
            mGpuSkinningEnabled = mShader.GetFusion(HardwareSkinningFusion.NAME).Enabled;
            mGpuInstancingEnabled = mShader.GetFusion(HardwareInstancingFusion.NAME).Enabled;
            mBumpMappingEnabled = mShader.GetFusion(NormalMapFusion.NAME).Enabled;
            mUVChannelCount = mShader.UVChannelCount;

            /*
            mNeedViewAndNormalVector =
                (!mShader.Transparent) ||
                (Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination).Count > 0) ||
                (Utils.GetEnabledFusionsByClass(mShader, Stage.Relfection).Count > 0) ||
                (Utils.GetEnabledFusionsByClass(mShader, Stage.Override).Count > 0);
            */
            mNeedViewAndNormalVector = true;
            switch(codeFile)
            {
                case ".material":
                    CodeMaterial(b);
                    break;

                case ".shd":
                    CodeShd(b);
                    break;
            }
        }

        private int mUVChannelCount;
        private bool mGpuSkinningEnabled;
        private bool mGpuInstancingEnabled;
        private bool mBumpMappingEnabled;
        private bool mNeedViewAndNormalVector;
        private const string SCHEME_DEFAULT = "Default";
        private const string SCHEME_DEPTHNORMAL = "Deferred";
        private const string SCHEME_SHADOWMAP = "ShadowMap";
        private const string SCHEME_IMPOSTOR = "Impostor";

        #region Material
        private void CodeMaterial(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
            b.AppendLine("vertex_program " + mShader.Name + "/Vert hlsl");
            b.AppendLine("{");
            b.AppendLine("source " + mShader.Name + ".shd");
            b.AppendLine("entry_point Vert");
            b.AppendLine("target vs_3_0");
            b.AppendLine("preprocessor_defines HLSL=1");
            if (mGpuSkinningEnabled)
                b.AppendLine("uses_vertex_texture_fetch true");
            b.AppendLine("} // vertex_program");

            {
                b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
                b.AppendLine("fragment_program " + mShader.Name + "/Frag_" + SCHEME_DEFAULT + " hlsl");
                b.AppendLine("{");
                b.AppendLine("source " + mShader.Name + ".shd");
                b.AppendLine("entry_point Frag_" + SCHEME_DEFAULT);
                b.AppendLine("target ps_3_0");
                b.AppendLine("preprocessor_defines HLSL=1");
                b.AppendLine("} // fragment_program");
            }

            {
                b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
                b.AppendLine("fragment_program " + mShader.Name + "/Frag_" + SCHEME_DEPTHNORMAL + " hlsl");
                b.AppendLine("{");
                b.AppendLine("source " + mShader.Name + ".shd");
                b.AppendLine("entry_point Frag_" + SCHEME_DEPTHNORMAL);
                b.AppendLine("target ps_3_0");
                b.AppendLine("preprocessor_defines HLSL=1");
                b.AppendLine("} // fragment_program");
            }

            {
                b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
                b.AppendLine("fragment_program " + mShader.Name + "/Frag_" + SCHEME_SHADOWMAP + " hlsl");
                b.AppendLine("{");
                b.AppendLine("source " + mShader.Name + ".shd");
                b.AppendLine("entry_point Frag_" + SCHEME_SHADOWMAP);
                b.AppendLine("target ps_3_0");
                b.AppendLine("preprocessor_defines HLSL=1");
                b.AppendLine("} // fragment_program");
            }

            {
                b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
                b.AppendLine("fragment_program " + mShader.Name + "/Frag_" + SCHEME_IMPOSTOR + " hlsl");
                b.AppendLine("{");
                b.AppendLine("source " + mShader.Name + ".shd");
                b.AppendLine("entry_point Frag_" + SCHEME_IMPOSTOR);
                b.AppendLine("target ps_3_0");
                b.AppendLine("preprocessor_defines HLSL=1");
                b.AppendLine("} // fragment_program");
            }

            b.AppendLine("//--------//--------//--------//--------//--------//--------//--------//--------");
            b.AppendLine("material " + mShader.Name);
            b.AppendLine("{");

            // default technique
            {
                b.AppendLine("technique " + SCHEME_DEFAULT);
                b.AppendLine("{");
                b.AppendLine("scheme " + SCHEME_DEFAULT);
                b.AppendLine("pass Pass0");
                b.AppendLine("{");

                if (mShader.DepthTest)
                    b.AppendLine("depth_check on");
                else
                    b.AppendLine("depth_check off");

                if (mShader.Transparent)
                {
                    b.AppendLine("scene_blend alpha_blend");
                }

                if (mShader.Transparent || mShader.GetFusion(DistortionFusion.NAME).Enabled)
                {
                    b.AppendLine("depth_write off");
                }
                else
                {
                    b.AppendLine("depth_write on");
                }

                if (mShader.TwoSided)
                {
                    b.AppendLine("cull_hardware none");
                    b.AppendLine("cull_software none");
                }

                b.AppendLine("//--------");
                b.AppendLine("vertex_program_ref " + mShader.Name + "/Vert");
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // vertex_program_ref");

                b.AppendLine("//--------");
                b.AppendLine("fragment_program_ref " + mShader.Name + "/Frag_" + SCHEME_DEFAULT);
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // fragment_program_ref");

                CodeMaterialTextureUnits(b);

                b.AppendLine("} // pass");
                b.AppendLine("} // technique");
            }

            // depth and normal technique
            {
                b.AppendLine("technique " + SCHEME_DEPTHNORMAL);
                b.AppendLine("{");
                b.AppendLine("scheme " + SCHEME_DEPTHNORMAL);
                b.AppendLine("pass Pass0");
                b.AppendLine("{");
                b.AppendLine("depth_check on");
                b.AppendLine("depth_write on");

                if (mShader.TwoSided)
                {
                    b.AppendLine("cull_hardware none");
                    b.AppendLine("cull_software none");
                }

                b.AppendLine("//--------");
                b.AppendLine("vertex_program_ref " + mShader.Name + "/Vert");
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // vertex_program_ref");

                b.AppendLine("//--------");
                b.AppendLine("fragment_program_ref " + mShader.Name + "/Frag_" + SCHEME_DEPTHNORMAL);
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // fragment_program_ref");

                CodeMaterialTextureUnits(b);

                b.AppendLine("} // pass");
                b.AppendLine("} // technique");
            }

            // shadow map technique
            {
                b.AppendLine("technique " + SCHEME_SHADOWMAP);
                b.AppendLine("{");
                b.AppendLine("scheme " + SCHEME_SHADOWMAP);
                b.AppendLine("pass Pass0");
                b.AppendLine("{");
                b.AppendLine("depth_check on");
                b.AppendLine("depth_write on");

                if (mShader.TwoSided)
                {
                    b.AppendLine("cull_hardware none");
                    b.AppendLine("cull_software none");
                }

                b.AppendLine("//--------");
                b.AppendLine("vertex_program_ref " + mShader.Name + "/Vert");
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // vertex_program_ref");

                b.AppendLine("//--------");
                b.AppendLine("fragment_program_ref " + mShader.Name + "/Frag_" + SCHEME_SHADOWMAP);
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // fragment_program_ref");

                CodeMaterialTextureUnits(b);

                b.AppendLine("} // pass");
                b.AppendLine("} // technique");
            }

            // impostor technique
            {
                b.AppendLine("technique " + SCHEME_IMPOSTOR);
                b.AppendLine("{");
                b.AppendLine("scheme " + SCHEME_IMPOSTOR);
                b.AppendLine("pass Pass0");
                b.AppendLine("{");
                b.AppendLine("depth_check on");
                b.AppendLine("depth_write on");

                if (mShader.TwoSided)
                {
                    b.AppendLine("cull_hardware none");
                    b.AppendLine("cull_software none");
                }

                b.AppendLine("//--------");
                b.AppendLine("vertex_program_ref " + mShader.Name + "/Vert");
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // vertex_program_ref");

                b.AppendLine("//--------");
                b.AppendLine("fragment_program_ref " + mShader.Name + "/Frag_" + SCHEME_IMPOSTOR);
                b.AppendLine("{");
                CodeMaterialConstants(b);
                CodeMaterialParameters(b);
                b.AppendLine("} // fragment_program_ref");

                CodeMaterialTextureUnits(b);

                b.AppendLine("} // pass");
                b.AppendLine("} // technique");
            }

            // end of material
            b.AppendLine("} // material");
        }

        private void CodeMaterialParameters(StringBuilder b)
        {
            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        if (p is VectorParamter && (p as VectorParamter).UserData != null)
                        {
                            b.AppendLine(String.Format("param_named_auto {0} custom {1}", p.Name, (p as VectorParamter).UserData));
                        }
                        else if (p.GetType() == typeof(ScalarParameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float {1}", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec2Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float2 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(Vec3Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float3 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(Vec4Parameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float4 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                        else if (p.GetType() == typeof(ColorParameter))
                        {
                            b.AppendLine(String.Format("param_named {0} float4 {1}", p.Name, p.Value.Replace(",", "")));
                        }
                    }
                }
            }

            if (mGpuInstancingEnabled)
            {
                b.AppendLine("param_named_auto cNumVertices custom 76");
            }
        }

        private void CodeMaterialTextureUnits(StringBuilder b)
        {
            // texture unit
            int texunit = 0;

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        if (p.GetType() == typeof(Texture2dParamter))
                        {
                            Texture2dParamter ptex2d = p as Texture2dParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptex2d.ForceSamplerId >= 0) tunit = ptex2d.ForceSamplerId;

                            string path = "";
                            if (ptex2d.Path.Length > 0)
                                path = "texture " + System.IO.Path.GetFileName(ptex2d.Path) + " 2d";

                            b.AppendLine("//--------");
                            b.Append(String.Format
                                ("texture_unit {0}\n{{\n{1} {2}\nfiltering {3} {4} {5}\ntex_address_mode {6} {7}\ntex_coord_set {8}\nbinding_type {9}\n}} // texture_unit\n"
                                , ptex2d.Name
                                , path
                                , ptex2d.SRGBTexture ? "gamma" : ""
                                , GetTextureFilterName(ptex2d.MinFilter).ToLower()
                                , GetTextureFilterName(ptex2d.MagFilter).ToLower()
                                , GetTextureFilterName(ptex2d.MipFilter).ToLower()
                                , GetTextureAddressMode(ptex2d.UAddressMode).ToLower()
                                , GetTextureAddressMode(ptex2d.VAddressMode).ToLower()
                                , tunit
                                , ptex2d.UserData
                                ));
                        }
                        else if (p.GetType() == typeof(TextureCubeParamter))
                        {
                            TextureCubeParamter ptexCube = p as TextureCubeParamter;
                            
                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptexCube.ForceSamplerId >= 0) tunit = ptexCube.ForceSamplerId;

                            string path = "";
                            if (ptexCube.Path.Length > 0)
                                path = "texture " + System.IO.Path.GetFileName(ptexCube.Path) + " cubic";
                            
                            b.AppendLine("//--------");
                            b.Append(String.Format
                                ("texture_unit {0}\n{{\nbinding_type fragment\n{1} {2}\nfiltering {3} {4} {5}\ntex_address_mode {6} {7} {8}\ntex_coord_set {9}\nbinding_type {10}\n}} // texture_unit\n"
                                , ptexCube.Name
                                , path
                                , ptexCube.SRGBTexture ? "gamma" : ""
                                , GetTextureFilterName(ptexCube.MinFilter).ToLower()
                                , GetTextureFilterName(ptexCube.MagFilter).ToLower()
                                , GetTextureFilterName(ptexCube.MipFilter).ToLower()
                                , GetTextureAddressMode(ptexCube.UAddressMode).ToLower()
                                , GetTextureAddressMode(ptexCube.VAddressMode).ToLower()
                                , GetTextureAddressMode(ptexCube.WAddressMode).ToLower()
                                , tunit
                                , ptexCube.UserData
                                ));
                        }
                    }
                }
            }            
        }

        private static void CodeMaterialConstants(StringBuilder b)
        {
            b.AppendLine("param_named_auto cWorldMtx world_matrix ");
            b.AppendLine("param_named_auto cWorldViewMtx worldview_matrix ");
            b.AppendLine("param_named_auto cWorldViewProjMtx worldviewproj_matrix");
            b.AppendLine("param_named_auto cViewMtx view_matrix ");
            b.AppendLine("param_named_auto cViewIMtx inverse_view_matrix");
            b.AppendLine("param_named_auto cProjMtx projection_matrix");
            b.AppendLine("param_named_auto cTime time");
            b.AppendLine("param_named_auto cSceneDepthRange scene_depth_range");
            b.AppendLine("param_named_auto cViewportSize viewport_size");
            b.AppendLine("param_named_auto cCustom0 custom 0");
            b.AppendLine("param_named_auto cCustom1 custom 1");
            b.AppendLine("param_named_auto cCustom2 custom 2");
            b.AppendLine("param_named_auto cCustom3 custom 3");
        }

        #endregion // Material

        #region Shd

        private void CodeShd(StringBuilder b)
        {
            CodeShdPreprocessor(b);

            CodeShdHeader(b);

            CodeShdParameter(b);

            CodeShdConstant(b);

            CodeShdVertHeader(b);
            CodeShdVertBody(b);
            CodeShdVertFooter(b);

            CodeShdFragHeader(b, SCHEME_DEFAULT);
            CodeShdFragVarying(b);
            CodeShdFragBody(b, SCHEME_DEFAULT);
            CodeShdFragFooter(b, SCHEME_DEFAULT);

            CodeShdFragHeader(b, SCHEME_DEPTHNORMAL);
            CodeShdFragVarying(b);
            CodeShdFragBody(b, SCHEME_DEPTHNORMAL);
            CodeShdFragFooter(b, SCHEME_DEPTHNORMAL);

            CodeShdFragHeader(b, SCHEME_SHADOWMAP);
            CodeShdFragVarying(b);
            CodeShdFragBody(b, SCHEME_SHADOWMAP);
            CodeShdFragFooter(b, SCHEME_SHADOWMAP);

            CodeShdFragHeader(b, SCHEME_IMPOSTOR);
            CodeShdFragVarying(b);
            CodeShdFragBody(b, SCHEME_IMPOSTOR);
            CodeShdFragFooter(b, SCHEME_IMPOSTOR);
            
            CodeShdFooter(b);
        }

        private void CodeShdHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("//Fx Header");

            b.AppendLine("typedef float4x4 mat4;");
            b.AppendLine("typedef float3x3 mat3;");
            b.AppendLine("typedef float4 vec4;");
            b.AppendLine("typedef float3 vec3;");
            b.AppendLine("typedef float2 vec2;");

            b.AppendLine("vec4 xform(mat4 m, vec4 v) { return mul(m, v); }");
            b.AppendLine("vec3 xform(mat3 m, vec3 v) { return mul(m, v); }");

            //b.AppendLine("vec2 packFloatToVec2i(in float value)");
            //b.AppendLine("{");
            //b.AppendLine("    const vec4 bitSh = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);");
            //b.AppendLine("    const vec4 bitMsk = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);");
            //b.AppendLine("    vec2 res = frac(value * bitSh.zw);");
            //b.AppendLine("    res -= res.xx * bitMsk;");
            //b.AppendLine("    return res;");
            //b.AppendLine("}");

            b.AppendLine("struct VpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec3 Norm         : NORMAL;");
            b.AppendLine("    vec3 Tang         : TANGENT;");
            b.AppendLine("    vec4 UV0          : TEXCOORD0;");
            if (mUVChannelCount > 1)
                b.AppendLine("    vec4 UV1          : TEXCOORD1;");
            if (mUVChannelCount > 2)
                b.AppendLine("    vec4 UV2          : TEXCOORD2;");
            //b.AppendLine("    vec2 UV3          : TEXCOORD3;");

            if (mGpuInstancingEnabled)
            {
                b.AppendLine("    vec4 Reserved0    : TEXCOORD4;");
                b.AppendLine("    vec4 Reserved1    : TEXCOORD5;");
                b.AppendLine("    vec4 Reserved2    : TEXCOORD6;");
                b.AppendLine("    vec4 Reserved3    : TEXCOORD7;");
            }

            if (mGpuSkinningEnabled)
            {
                b.AppendLine("    vec4 BoneWeights	: BLENDWEIGHT;");
                b.AppendLine("    vec4 BoneIndices  : BLENDINDICES;");
            }

            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("};");

            b.AppendLine("struct VpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos          : POSITION;");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec4 UV01         : TEXCOORD0;");
            b.AppendLine("    vec4 UV23         : TEXCOORD1;");
            b.AppendLine("    vec3 V_Vs         : TEXCOORD2;");
            b.AppendLine("    vec3 T_Vs         : TEXCOORD3;");
            b.AppendLine("    vec3 B_Vs         : TEXCOORD4;");
            b.AppendLine("    vec3 N_Vs         : TEXCOORD5;");
            b.AppendLine("    vec4 Pos_Cs       : TEXCOORD6;");
            b.AppendLine("};");

            b.AppendLine("struct VpPipeline");
            b.AppendLine("{");
            b.AppendLine("    vec4 Pos_Ms;");
            b.AppendLine("    vec3 T_Ms;");
           // b.AppendLine("    vec3 B_Ms;");
            b.AppendLine("    vec3 N_Ms;");
            b.AppendLine("    vec4 Color;");
            b.AppendLine("    vec2 UV0;");
            if (mUVChannelCount > 1)
                b.AppendLine("    vec2 UV1;");
            if (mUVChannelCount > 2)
                b.AppendLine("    vec2 UV2;");
            //b.AppendLine("    vec2 UV3;");
            b.AppendLine("};");

            b.AppendLine("struct FpIn");
            b.AppendLine("{");
            b.AppendLine("    vec4 Color        : COLOR0;");
            b.AppendLine("    vec4 UV01         : TEXCOORD0;");
            b.AppendLine("    vec4 UV23         : TEXCOORD1;");
            b.AppendLine("    vec3 V_Vs         : TEXCOORD2;");
            b.AppendLine("    vec3 T_Vs         : TEXCOORD3;");
            b.AppendLine("    vec3 B_Vs         : TEXCOORD4;");
            b.AppendLine("    vec3 N_Vs         : TEXCOORD5;");
            b.AppendLine("    vec4 Pos_Cs       : TEXCOORD6;");
            b.AppendLine("    float Face        : VFACE;");
            b.AppendLine("};");

            b.AppendLine("struct FpOut");
            b.AppendLine("{");
            b.AppendLine("    vec4 C0           : COLOR0;");
            b.AppendLine("};");
            
            b.AppendLine("struct FpOut1");
            b.AppendLine("{");
            b.AppendLine("    vec4 C0           : COLOR0;");
            b.AppendLine("    vec4 C1           : COLOR1;");
            b.AppendLine("};");

            b.AppendLine("struct FpPipeline");
            b.AppendLine("{");
            b.AppendLine("  vec2  Texcoord[3];");
            b.AppendLine("  vec3  ViewDir;");
            b.AppendLine("  vec3  Normal;");
            b.AppendLine("  vec3  Albedo;");
            b.AppendLine("  vec3  Gloss;");
            b.AppendLine("  float Opacity;");
            b.AppendLine("  vec3  Illumination;");
            b.AppendLine("  vec3  Reflection;");
            b.AppendLine("  vec3  FragColor;");
            b.AppendLine("};");
        }

        private void CodeShdPreprocessor(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Preprocessors");

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (Preprocessor p in f.Preprocessors)
                    {
                        //string line = "#define " + p.Name + " " + p.Value.ToString();
                        string line = String.Format("#define {0} {1}", p.Name, p.Value);
                        b.AppendLine(line);
                    }
                }
            }
        }

        private void CodeShdParameter(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Parameters");

            int texunit = 0;

            foreach (IFusion f in mShader.Fusions)
            {
                if (null != f && f.Enabled)
                {
                    foreach (IParameter p in f.Parameters)
                    {
                        if (p.GetType() == typeof(Texture2dParamter))
                        {
                            Texture2dParamter ptex2d = p as Texture2dParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptex2d.ForceSamplerId >= 0) tunit = ptex2d.ForceSamplerId;

                            b.AppendLine(String.Format
                                ("uniform sampler2D {0} : register( s{1} );"
                                , ptex2d.SamplerName
                                , tunit
                                ));
                        }
                        else if (p.GetType() == typeof(TextureCubeParamter))
                        {
                            TextureCubeParamter ptexCube = p as TextureCubeParamter;

                            //todo: handle collided sampler for ptex2d.ForceSamplerId
                            int tunit = texunit++;
                            if (ptexCube.ForceSamplerId >= 0) tunit = ptexCube.ForceSamplerId;
                            
                            b.AppendLine(String.Format
                                ("uniform samplerCUBE {0} : register( s{1} );"
                                , ptexCube.SamplerName
                                , tunit
                                ));
                        }
                        else if (p.GetType() == typeof(ScalarParameter))
                        {
                            b.AppendLine(String.Format("uniform float {0} = {1};", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec2Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec2 {0} = vec2({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec3Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec3 {0} = vec3({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(Vec4Parameter))
                        {
                            b.AppendLine(String.Format("uniform vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                        else if (p.GetType() == typeof(ColorParameter))
                        {
                            b.AppendLine(String.Format("uniform vec4 {0} = vec4({1});", p.Name, p.Value));
                        }
                    }
                }
            }

            if (mGpuInstancingEnabled)
            {
                b.AppendLine("uniform float4 cNumVertices; // Only used for XBox instancing only");
            }
        }

        private void CodeShdFooter(StringBuilder b)
        {
        }

        private void CodeShdConstant(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Constants");
            b.AppendLine("uniform mat4 cWorldMtx;");
            b.AppendLine("uniform mat4 cWorldViewMtx;");
            b.AppendLine("uniform mat4 cWorldViewProjMtx;");
            b.AppendLine("uniform mat4 cViewMtx;");
            b.AppendLine("uniform mat4 cViewIMtx;");
            b.AppendLine("uniform mat4 cProjMtx;");
            b.AppendLine("uniform float cTime;");
            b.AppendLine("uniform float4 cSceneDepthRange; // HwDeferredRS only");
            b.AppendLine("uniform float4 cViewportSize; // HwDeferredRS only");
            b.AppendLine("uniform float4 cCustom0; // Reserved per-object color & alpha");
            b.AppendLine("uniform float4 cCustom1; // Reserved per-object uv control");
            b.AppendLine("uniform float4 cCustom2; // Reserved per-object uv control");
            b.AppendLine("uniform float4 cCustom3; // Reserved per-object user constant");            
        }

        #region Vertex Program
        private void CodeShdVertHeader(StringBuilder b)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Vertex0 program");
            
            if (mGpuInstancingEnabled)
            {
                // PC 
                #region mGpuInstancingEnabled PC
                b.AppendLine("#ifndef _XBOX");
                {
                    b.AppendLine("void Vert(VpIn input, out VpOut output)");
                    b.AppendLine("{");
                    b.AppendLine("VpPipeline pipeline;");
                }
                #endregion
                b.AppendLine("#else");
                // XBOX
                #region mGpuInstancingEnabled XBOX
                {
                    b.AppendLine("void Vert(int nIndex : INDEX, out VpOut output)");
                    b.AppendLine("{");
                    b.AppendLine("VpPipeline pipeline;");

                    b.AppendLine("VpIn input = (VpIn)0;");
                    b.AppendLine("int vertex_index = (nIndex + 0.5) % cNumVertices.x;");
                    b.AppendLine("int instance_index = (nIndex + 0.5) / cNumVertices.x;");
                    b.AppendLine("int4 vi4;");

                    // Fetch the vertex index
                    b.AppendLine("asm {");
                    {
                        b.AppendLine("vfetch vi4, vertex_index, position1");
                    }
                    b.AppendLine("};");

                    b.AppendLine("vertex_index = vi4.x;");

                    b.AppendLine("{");                    

                    {
                        b.AppendLine("vec4 input_pos;");
                        b.AppendLine("vec4 input_norm;");
                        b.AppendLine("vec4 input_tang;");

                        b.AppendLine("vec4 input_uv0;");
                        b.AppendLine("vec4 input_uv1;");
                        b.AppendLine("vec4 input_uv2;");
                        b.AppendLine("vec4 input_uv3;");

                        b.AppendLine("vec4 input_reserved0;");
                        b.AppendLine("vec4 input_reserved1;");
                        b.AppendLine("vec4 input_reserved2;");
                        b.AppendLine("vec4 input_reserved3;");

                        if (mGpuSkinningEnabled)
                        {
                            b.AppendLine("vec4 input_boneweights;");
                            b.AppendLine("vec4 input_boneindices;");
                        }

                        b.AppendLine("vec4 input_color;");

                        // Fetch the vertex data
                        b.AppendLine("asm {");
                        {
                            b.AppendLine("vfetch input_pos, vertex_index, position0");
                            b.AppendLine("vfetch input_norm, vertex_index, normal0");
                            b.AppendLine("vfetch input_tang, vertex_index, tangent0");

                            b.AppendLine("vfetch input_uv0, vertex_index, texcoord0");
                            b.AppendLine("vfetch input_uv1, vertex_index, texcoord1");
                            b.AppendLine("vfetch input_uv2, vertex_index, texcoord2");
                            //b.AppendLine("vfetch input_uv3, vertex_index, texcoord3");

                            b.AppendLine("vfetch input_reserved0, instance_index, texcoord4");
                            b.AppendLine("vfetch input_reserved1, instance_index, texcoord5");
                            b.AppendLine("vfetch input_reserved2, instance_index, texcoord6");
                            b.AppendLine("vfetch input_reserved3, instance_index, texcoord7");

                            if (mGpuSkinningEnabled)
                            {
                                b.AppendLine("vfetch input_boneweights, vertex_index, blendweight0");
                                b.AppendLine("vfetch input_boneindices, vertex_index, blendindices0");
                            }

                            b.AppendLine("vfetch input_color, vertex_index, color0");

                        }
                        b.AppendLine("};");

                        b.AppendLine("input.Pos = input_pos;");
                        b.AppendLine("input.Norm = input_norm.xyz;");
                        b.AppendLine("input.Tang = input_tang.xyz;");

                        b.AppendLine("input.UV0 = input_uv0;");
                        if (mUVChannelCount > 1)
                            b.AppendLine("input.UV1 = input_uv1;");
                        if (mUVChannelCount > 2)
                            b.AppendLine("input.UV2 = input_uv2;");
                        //b.AppendLine("input.UV3 = input_uv3.xy;");

                        b.AppendLine("input.Reserved0 = input_reserved0;");
                        b.AppendLine("input.Reserved1 = input_reserved1;");
                        b.AppendLine("input.Reserved2 = input_reserved2;");
                        b.AppendLine("input.Reserved3 = input_reserved3;");                        

                        if (mGpuSkinningEnabled)
                        {
                            b.AppendLine("input.BoneWeights = input_boneweights;");
                            b.AppendLine("input.BoneIndices = input_boneindices;");
                        }

                        b.AppendLine("input.Color = input_color;");

                    }
                    b.AppendLine("}");

                }
                #endregion
                b.AppendLine("#endif");
            }
            else
            {
                b.AppendLine("void Vert(VpIn input, out VpOut output)");
                b.AppendLine("{");
                b.AppendLine("VpPipeline pipeline = (VpPipeline)0;");
            }

            
        }

        private void CodeShdVertFooter(StringBuilder b)
        {
            b.AppendLine("}");
        }

        private void CodeShdVertBody(StringBuilder b)
        {                                    
            b.AppendLine("// Vertex0 pipeline init");
            b.AppendLine("pipeline.Pos_Ms = input.Pos.xyzw;");
            b.AppendLine("pipeline.N_Ms = input.Norm.xyz;");
            b.AppendLine("pipeline.T_Ms = input.Tang.xyz;");
            b.AppendLine("pipeline.Color = input.Color;");
            b.AppendLine("pipeline.UV0 = input.UV0;");
            if (mUVChannelCount > 1)
                b.AppendLine("pipeline.UV1 = input.UV1;");
            if (mUVChannelCount > 2)
                b.AppendLine("pipeline.UV2 = input.UV2;");
            //b.AppendLine("pipeline.UV3 = input.UV3;");

            // vertex fusions
            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex0))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex1))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex2))
                AppendFeatureCode(f, b);

            foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Vertex3))
                AppendFeatureCode(f, b);

            b.AppendLine("// Vertex pipeline output");
            b.AppendLine("output = (VpOut)0;");
            b.AppendLine("output.Pos  = xform(cWorldViewProjMtx, pipeline.Pos_Ms);");
            b.AppendLine("output.Pos_Cs = output.Pos * vec4(1, -1, 1, 1);");

            if (mNeedViewAndNormalVector)
            {
                b.AppendLine("output.V_Vs = xform(cWorldViewMtx, pipeline.Pos_Ms).xyz;");
                b.AppendLine("output.N_Vs = xform((mat3)cWorldViewMtx, pipeline.N_Ms).xyz;");
            }

            // we only compute the tangent and bitangent only at normal mapping fusion is enabled
            if (mBumpMappingEnabled)
            {
                b.AppendLine("output.T_Vs = xform((mat3)cWorldViewMtx, pipeline.T_Ms).xyz;");
                b.AppendLine("output.B_Vs = cross(output.T_Vs, output.N_Vs);");

                // apply the flipping after the cross(output.T_Vs, output.N_Vs) 
                if(!mShader.GetFusion(TangentFlippingFusion.NAME).Enabled)
                    b.AppendLine("output.B_Vs *= -1;");
            }
            
            b.AppendLine("output.Color = pipeline.Color;");

            b.AppendLine("output.UV01.xy = pipeline.UV0;");
            if (mUVChannelCount > 1)
                b.AppendLine("output.UV01.zw = pipeline.UV1;");
            if (mUVChannelCount > 2)
                b.AppendLine("output.UV23.xy = pipeline.UV2;");
            //b.AppendLine("output.UV23.zw = pipeline.UV3;");
        }

        #endregion // Vertex0 Program

        #region Fragment Program
        private void CodeShdFragHeader(StringBuilder b, string scheme)
        {
            b.AppendLine("//--------//--------//--------//--------//--------");
            b.AppendLine("// Fragment program " + scheme);
            if(scheme == SCHEME_IMPOSTOR)
                b.AppendLine("void Frag_" + scheme + "(FpIn input, out FpOut1 output)");
            else
                b.AppendLine("void Frag_" + scheme + "(FpIn input, out FpOut output)");
            b.AppendLine("{");
            b.AppendLine("vec2 cReprojUV = ( input.Pos_Cs.xy / input.Pos_Cs.ww ) * 0.5 + 0.5 + ( cViewportSize.zw * 0.5 );");
        }

        private void CodeShdFragVarying(StringBuilder b)
        {
            b.AppendLine("// Fragment pipeline init");
            b.AppendLine("FpPipeline pipeline;");
            b.AppendLine("pipeline.Texcoord[0] = input.UV01.xy;");
            b.AppendLine("pipeline.Texcoord[1] = input.UV01.zw;");
            b.AppendLine("pipeline.Texcoord[2] = input.UV23.xy;");

            b.AppendLine("pipeline.ViewDir = normalize(input.V_Vs);");
            b.AppendLine("pipeline.Normal = normalize(input.N_Vs);");
            b.AppendLine("pipeline.Albedo = input.Color.rgb * cCustom0.rgb;");
            b.AppendLine("pipeline.Gloss = vec3(1,1,1);");
            b.AppendLine("pipeline.Opacity = input.Color.a * cCustom0.a;");
            b.AppendLine("pipeline.Illumination = vec3(0,0,0);");
            b.AppendLine("pipeline.Reflection = vec3(0,0,0);");
            b.AppendLine("pipeline.FragColor = vec3(0,0,0);");
        }

        private void CodeShdFragBody(StringBuilder b, string scheme)
        {
            if (SCHEME_DEFAULT == scheme)
            {
                // parallax
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                    AppendFeatureCode(f, b);

                // opacity
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                    AppendFeatureCode(f, b);

                // albedo & gloss
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.AlbedoGloss))
                    AppendFeatureCode(f, b);

                // illumination
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination))
                    AppendFeatureCode(f, b);

                // set illumination to 1 if no illumination-features exists
                if (0 == Utils.GetEnabledFusionsByClass(mShader, Stage.Illumination).Count)
                    b.AppendLine("pipeline.Illumination = vec3(1,1,1);");

                // reflection
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Relfection))
                    AppendFeatureCode(f, b);

                // frag color
                b.AppendLine("pipeline.FragColor = pipeline.Albedo * pipeline.Illumination + pipeline.Reflection * pipeline.Gloss;");

                // override
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Override))
                    AppendFeatureCode(f, b);
            }
            else if(SCHEME_DEPTHNORMAL == scheme)
            {
                // parallax
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                    AppendFeatureCode(f, b);

                // opacity
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                    AppendFeatureCode(f, b);
            }
            else if (SCHEME_SHADOWMAP == scheme)
            {
                // parallax
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                    AppendFeatureCode(f, b);

                // opacity
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                    AppendFeatureCode(f, b);
            }
            else if (SCHEME_IMPOSTOR == scheme)
            {
                // parallax
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Parallex))
                    AppendFeatureCode(f, b);

                // opacity
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.Opacity))
                    AppendFeatureCode(f, b);

                // albedo & gloss
                foreach (IFusion f in Utils.GetEnabledFusionsByClass(mShader, Stage.AlbedoGloss))
                    AppendFeatureCode(f, b);
            }
        }

        private void CodeShdFragFooter(StringBuilder b, string scheme)
        {
            b.AppendLine("// Fragment pipeline output");

            if (SCHEME_DEFAULT == scheme)
            {
                // uncomment the following lines for debug purposes
                //b.AppendLine("pipeline.FragColor = input.Color;");
                //b.AppendLine("pipeline.FragColor = vec3(input.UV23.z, 1-input.UV23.w, 1);");
                //b.AppendLine("pipeline.FragColor = pipeline.Normal * 0.5 + 0.5;");
                //b.AppendLine("pipeline.FragColor = pipeline.Opacity;");
                //b.AppendLine("pipeline.FragColor = input.Pos_Cs.zzz / input.Pos_Cs.www;");
                // uncomment the above lines for debug purposes

                b.AppendLine("output.C0.rgb = pipeline.FragColor;");
                b.AppendLine("output.C0.a = pipeline.Opacity;");
            }
            else if (SCHEME_DEPTHNORMAL == scheme)
            {
                //b.AppendLine("float d = -input.V_Vs.z;");
                //b.AppendLine("d = saturate((d - cSceneDepthRange.x) * cSceneDepthRange.w) + (1/256.0);");
                //b.AppendLine("vec2 d_i = packFloatToVec2i(d);");

                //b.AppendLine("output.C0.rg = pipeline.Normal.xy * 0.5 + 0.5;");
                //b.AppendLine("output.C0.ba = d_i.xy;");
                b.AppendLine("output.C0.rgb = pipeline.Normal.xyz;");
                b.AppendLine("output.C0.a = input.V_Vs.z;");
            }
            else if (SCHEME_SHADOWMAP == scheme)
            {
                b.AppendLine("output.C0.rgba = input.V_Vs.zzzz;");
            }
            else if (SCHEME_IMPOSTOR == scheme)
            {
                b.AppendLine("output.C0.rgb = pipeline.Albedo.rgb;");
                b.AppendLine("output.C0.a = 1;");

                b.AppendLine("output.C1.rgb = pipeline.Normal.xyz * 0.5 + 0.5;");
                b.AppendLine("output.C1.a = 1;");
            }
            b.AppendLine("}");
        }

        #endregion // Fragment Program

        #endregion // Shd

    }
}
